package net.dotool.geo;

import java.io.Serializable;

import org.apache.commons.collections.FastHashMap;

import net.dotool.StringTools;

/**
 * 
 * @author 李岩飞
 *
 */
public class UTMRef implements Serializable {
	private static final long serialVersionUID = 5612722179068947622L;
	private FastHashMap attribMap = new FastHashMap();
	/**
	 * Easting
	 */
	private double easting;

	/**
	 * Northing
	 */
	private double northing;

	/**
	 * Latitude zone character
	 */
	private char latZone;

	/**
	 * Longitude zone number
	 */
	private int lngZone;

	public UTMRef(final int lngZone, final char latZone, final double easting, final double northing) {
		super();
		this.easting = easting;
		this.northing = northing;
		this.latZone = latZone;
		this.lngZone = lngZone;
	}

	public final UTMRef setAttrib(final String attribName, final Object attribObject) {
		this.attribMap.put(attribName, attribObject);
		return this;
	}

	public final Object getAttrib(final String attribName) {
		return this.attribMap.get(attribName);
	}

	/**
	 * Convert this UTM reference to a latitude and longitude.
	 * 
	 * @return the converted latitude and longitude
	 * @since 1.0
	 */
	public LatLng toLatLng() {
		final Ellipsoid wgsEllipsoid = EllipsoidDefs.getEllipsoid(23);
		final double a = wgsEllipsoid.getEquatorialRadius();
		final double eSquared = wgsEllipsoid.getSquareOfEccentricity();
		final double ePrimeSquared = eSquared / (1.0 - eSquared);
		final double e1 = (1 - Math.sqrt(1 - eSquared)) / (1 + Math.sqrt(1 - eSquared));
		final double x = easting - 500000.0;
		;
		double y = northing;
		final int zoneNumber = lngZone;
		final char zoneLetter = latZone;
		final double longitudeOrigin = (zoneNumber - 1.0) * 6.0 - 180.0 + 3.0;
		// Correct y for southern hemisphere
		if (zoneLetter - 'N' < 0) {
			y -= 10000000.0;
		}
		final double m = y / GeoUtils.SCALE_CENTRALMERIDIAN;
		final double mu = m / (a * (1.0 - eSquared / 4.0 - 3.0 * eSquared * eSquared / 64.0 - 5.0 * Math.pow(eSquared, 3.0) / 256.0));
		final double phi1Rad = mu + (3.0 * e1 / 2.0 - 27.0 * Math.pow(e1, 3.0) / 32.0) * Math.sin(2.0 * mu)
				+ (21.0 * e1 * e1 / 16.0 - 55.0 * Math.pow(e1, 4.0) / 32.0) * Math.sin(4.0 * mu) + 151.0 * Math.pow(e1, 3.0) / 96.0
				* Math.sin(6.0 * mu);
		final double n = a / Math.sqrt(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
		final double t = Math.tan(phi1Rad) * Math.tan(phi1Rad);
		final double c = ePrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
		final double r = a * (1.0 - eSquared) / Math.pow(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5);
		final double d = x / (n * GeoUtils.SCALE_CENTRALMERIDIAN);
		final double latitude = (phi1Rad - n
				* Math.tan(phi1Rad)
				/ r
				* (d * d / 2.0 - (5.0 + 3.0 * t + 10.0 * c - 4.0 * c * c - 9.0 * ePrimeSquared) * Math.pow(d, 4.0) / 24.0 + (61.0 + 90.0 * t + 298.0
						* c + 45.0 * t * t - 252.0 * ePrimeSquared - 3.0 * c * c)
						* Math.pow(d, 6.0) / 720.0))
				* 180.0 / Math.PI;
		final double longitude = longitudeOrigin
				+ (d - (1.0 + 2.0 * t + c) * Math.pow(d, 3.0) / 6.0 + (5.0 - 2.0 * c + 28.0 * t - 3.0 * c * c + 8.0 * ePrimeSquared + 24.0 * t * t)
						* Math.pow(d, 5.0) / 120.0) / Math.cos(phi1Rad) * 180.0 / Math.PI;
		return new LatLng(latitude, longitude);
	}

	/**
	 * Work out the UTM latitude zone from the latitude.
	 * 
	 * @param latitude
	 *            the latitude to find the UTM latitude zone for
	 * @return the UTM latitude zone for the given latitude
	 * @since 1.0
	 */
	public static char getUTMLatitudeZoneLetter(final double latitude) {
		if (84 >= latitude && latitude >= 72)
			return 'X';
		else if (72 > latitude && latitude >= 64)
			return 'W';
		else if (64 > latitude && latitude >= 56)
			return 'V';
		else if (56 > latitude && latitude >= 48)
			return 'U';
		else if (48 > latitude && latitude >= 40)
			return 'T';
		else if (40 > latitude && latitude >= 32)
			return 'S';
		else if (32 > latitude && latitude >= 24)
			return 'R';
		else if (24 > latitude && latitude >= 16)
			return 'Q';
		else if (16 > latitude && latitude >= 8)
			return 'P';
		else if (8 > latitude && latitude >= 0)
			return 'N';
		else if (0 > latitude && latitude >= -8)
			return 'M';
		else if (-8 > latitude && latitude >= -16)
			return 'L';
		else if (-16 > latitude && latitude >= -24)
			return 'K';
		else if (-24 > latitude && latitude >= -32)
			return 'J';
		else if (-32 > latitude && latitude >= -40)
			return 'H';
		else if (-40 > latitude && latitude >= -48)
			return 'G';
		else if (-48 > latitude && latitude >= -56)
			return 'F';
		else if (-56 > latitude && latitude >= -64)
			return 'E';
		else if (-64 > latitude && latitude >= -72)
			return 'D';
		else if (-72 > latitude && latitude >= -80)
			return 'C';
		else
			return 'Z';
	}

	/**
	 * @return the easting as longitude
	 */
	public double getEasting() {
		return easting;
	}

	/**
	 * @param easting
	 *            the easting to set
	 */
	public void setEasting(final double easting) {
		this.easting = easting;
	}

	/**
	 * @return the northing as latitude
	 */
	public double getNorthing() {
		return northing;
	}

	/**
	 * @param northing
	 *            the northing to set
	 */
	public void setNorthing(final double northing) {
		this.northing = northing;
	}

	/**
	 * @return the latZone
	 */
	public char getLatZone() {
		return latZone;
	}

	/**
	 * @param latZone
	 *            the latZone to set
	 */
	public void setLatZone(final char latZone) {
		this.latZone = latZone;
	}

	/**
	 * @return the lngZone
	 */
	public int getLngZone() {
		return lngZone;
	}

	/**
	 * @param lngZone
	 *            the lngZone to set
	 */
	public void setLngZone(final int lngZone) {
		this.lngZone = lngZone;
	}

	@Override
	public String toString() {
		// return lngZone + Character.toString(latZone) + " " + easting + " " + northing;
		return StringTools.u("UTMRef [easting=", easting, ", northing=", northing, ", latZone=", latZone, ", lngZone=", lngZone, "]");
	}
}
